/****************************************************************************
 * Chariott - Continuous Integration Testing Tool
 * Zaid Mohsin
 * The University of York
 * 2012/2013
 * MSc in Information Technology
 * All rights reserved.
 ****************************************************************************/
package chariott.testing;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.collections.MultiMap;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import chariott.configuration.Globals;
import chariott.partition.AlgorithmOrder;
import chariott.partition.BinPackingOrder_FF;
import chariott.partition.BinPackingOrder_FF_DEC;
import chariott.partition.LeastUsedOrder;
import chariott.partition.RoundRobinOrder;

public class AntScriptGenerator extends ScriptGenerator {

	private MultiMap myRunMap = null;
	private int numOfScripts = 0;

	/**
	 * ScriptGenerator class generates the Ant Scripts required to run on the
	 * instances based on the selection for an algorithm.
	 * 
	 * @param numOfNodes
	 * @param itemsToRun
	 * @param algorithmType
	 */
	public AntScriptGenerator(int numOfNodes, ArrayList<RunItem> itemsToRun) {

		super(numOfNodes, itemsToRun);

		// call the correct algorithm to fetch the corresponding run map for
		// each algorithm
		switch (Globals.ALGORITHM_TYPE) {
		case ROUNDROBIN:
			AlgorithmOrder RROrder = new RoundRobinOrder(numOfNodes, itemsToRun);
			myRunMap = RROrder.generateRuns();
			break;

		case LEASTUSED:
			AlgorithmOrder LUOrder = new LeastUsedOrder(numOfNodes, itemsToRun);
			myRunMap = LUOrder.generateRuns();
			break;

		case BINPACKING_FIRSTFIT:
			AlgorithmOrder BPFFOrder = new BinPackingOrder_FF(numOfNodes,
					itemsToRun, Double.parseDouble(Globals.BIN_SIZE));
			myRunMap = BPFFOrder.generateRuns();
			break;

		case BINPACKING_FF_DECREASING:
			AlgorithmOrder BPFFDECOrder = new BinPackingOrder_FF_DEC(
					numOfNodes, itemsToRun,
					Double.parseDouble(Globals.BIN_SIZE));
			myRunMap = BPFFDECOrder.generateRuns();
			break;

		case AUTO:
			myRunMap = CostEffectiveAlgorithmSelector.INSTANCE
					.getBestAlgorithm(numOfNodes, itemsToRun);

		default:
			break;
		}

		// get the number of scripts required.
		numOfScripts = myRunMap.size();

	}

	/**
	 * generate() returns an ArrayList of all the script Files generated from
	 * this class.
	 * 
	 * @return
	 */
	public ArrayList<File> generate() {

		ArrayList<File> scriptFiles = new ArrayList();

		for (int i = 0; i < numOfScripts; i++) {
			// create a document root for each script to run the each session
			Element docRoot = new Element("project");
			docRoot.setAttribute("name", Globals.TESTING_PROJECT_NAME);
			docRoot.setAttribute("basedir", ".");
			// add the classpath and SVN checkout target to the script
			docRoot.addContent(classpath());
			// add the init target to the script
			docRoot.addContent(initTarget());
			// add the run tests target to the script
			docRoot.addContent(runTestsTarget(i));
			// add the commit part of the script
			docRoot.addContent(commitTarget());
			// add the final part of the script to be run
			docRoot.addContent(allTarget());

			// generate the script file
			scriptFiles.add(outputFile(docRoot, i));
		}

		return scriptFiles;
	}

	/**
	 * writes the classpath Element part of the ant script
	 * 
	 * @return
	 */
	private Element classpath() {
		Element path = new Element("path");
		path.setAttribute("id", "classpath");
		Element pathelement1 = new Element("pathelement");
		pathelement1.setAttribute("location", "./"
				+ Globals.TESTING_PROJECT_NAME + "/lib/junit.jar");
		Element pathelement2 = new Element("pathelement");
		pathelement2.setAttribute("location", "./"
				+ Globals.TESTING_PROJECT_NAME + "/lib/hamcrest.jar");
		Element pathelement3 = new Element("pathelement");
		pathelement3.setAttribute("location", "./"
				+ Globals.TESTING_PROJECT_NAME + "/build");

		path.addContent(pathelement1);
		path.addContent(pathelement2);
		path.addContent(pathelement3);

		return path;
	}

	/**
	 * write the init target to check out from the SVN repository and build the
	 * project files
	 * 
	 * @return
	 */
	private Element initTarget() {
		// create calls to SVN and ant to build the project from reposioty
		Element initTarget = new Element("target");
		initTarget.setAttribute("name", "init");

		Element svnCheckOut = new Element("exec");
		svnCheckOut.setAttribute("executable", "svn");

		Element svnCheckOutArg1 = new Element("arg");
		svnCheckOutArg1.setAttribute("value", "co");
		Element svnCheckOutArg2 = new Element("arg");
		svnCheckOutArg2.setAttribute("value", Globals.SVN_REPOSITORY);

		svnCheckOut.addContent(svnCheckOutArg1);
		svnCheckOut.addContent(svnCheckOutArg2);

		// create call to ant to build the files

		Element antBuild = new Element("exec");
		antBuild.setAttribute("executable", "ant");

		Element antBuildArg1 = new Element("arg");
		antBuildArg1.setAttribute("value", "-buildfile");
		Element antBuildArg2 = new Element("arg");
		antBuildArg2.setAttribute("value", "./" + Globals.TESTING_PROJECT_NAME
				+ "/build.xml");

		antBuild.addContent(antBuildArg1);
		antBuild.addContent(antBuildArg2);

		initTarget.addContent(svnCheckOut);
		initTarget.addContent(antBuild);

		return initTarget;
	}

	private Element runTestsTarget(int nodeIndex) {
		// create the target headers
		Element runTestsTarget = new Element("target");
		runTestsTarget.setAttribute("name", "run-tests");
		runTestsTarget.setAttribute("depends", "init");
		// create parallel element to make the unit tests run in parallel in
		// each machine.
		// Element parallel = new Element("parallel");

		// create the junit headers to run according to the sorted map
		List<RunItem> myRunItems = (List<RunItem>) myRunMap.get(nodeIndex);

		// check if no run items were assigned to this node.
		if (myRunItems != null) {
			for (RunItem item : myRunItems) {

				Element junit = new Element("junit");
				junit.setAttribute("printsummary", "yes");
				junit.setAttribute("haltonfailure", "no");
				junit.setAttribute("showoutput", "yes");
				junit.setAttribute("fork", "no");

				Element classpath = new Element("classpath");
				classpath.setAttribute("refid", "classpath");

				junit.addContent(classpath);

				// create the format for result reports.
				Element formatter1 = new Element("formatter");
				formatter1.setAttribute("type", "plain");
				formatter1.setAttribute("usefile", "false");

				Element formatter2 = new Element("formatter");
				formatter2.setAttribute("type", "xml");

				// add formatters
				junit.addContent(formatter1);
				junit.addContent(formatter2);

				if (item instanceof TestCase) {
					Element test = new Element("test");
					TestCase myCase = (TestCase) item;
					test.setAttribute("name", myCase.getName());
					test.setAttribute("todir", Globals.TESTING_PROJECT_NAME
							+ "/" + Globals.TESTING_TEST_FOLDER);

					junit.addContent(test);
				} else if (item instanceof TestMethod) {
					Element test = new Element("test");
					TestMethod myMethod = (TestMethod) item;
					test.setAttribute("name", myMethod.getTestCaseName());
					test.setAttribute("methods", myMethod.getMethodName());
					test.setAttribute("todir", Globals.TESTING_PROJECT_NAME
							+ "/" + Globals.TESTING_TEST_FOLDER);

					junit.addContent(test);
				} else if (item instanceof TestSequence) {
					TestSequence mySequence = (TestSequence) item;
					for (TestCase myCase : mySequence.getTestCases()) {
						Element test = new Element("test");
						test.setAttribute("name", myCase.getName());
						test.setAttribute("todir", Globals.TESTING_PROJECT_NAME
								+ "/" + Globals.TESTING_TEST_FOLDER);
						junit.addContent(test);
					}

				} else
					try {
						throw new Exception("Unknown Object Passed!");
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				// parallel.addContent(junit);
				runTestsTarget.addContent(junit);
			}
		}

		// runTestsTarget.addContent(parallel);

		return runTestsTarget;
	}

	/**
	 * writes the commit back to SVN part in the ant script
	 * 
	 * @return
	 */
	private Element commitTarget() {
		Element commitTarget = new Element("target");
		commitTarget.setAttribute("name", "commit");
		commitTarget.setAttribute("depends", "run-tests");

		Element svnAdd = new Element("exec");
		svnAdd.setAttribute("executable", "svn");
		Element svnAddArg1 = new Element("arg");
		svnAddArg1.setAttribute("value", "add");
		Element svnAddArg2 = new Element("arg");
		svnAddArg2.setAttribute("value", "./" + Globals.TESTING_PROJECT_NAME
				+ "/" + Globals.TESTING_TEST_FOLDER);
		Element svnAddArg3 = new Element("arg");
		svnAddArg3.setAttribute("value", "--force");

		svnAdd.addContent(svnAddArg1);
		svnAdd.addContent(svnAddArg2);
		svnAdd.addContent(svnAddArg3);

		commitTarget.addContent(svnAdd);

		Element svnCommit = new Element("exec");
		svnCommit.setAttribute("executable", "svn");

		Element svnCommitArg1 = new Element("arg");
		svnCommitArg1.setAttribute("value", "commit");
		Element svnCommitArg2 = new Element("arg");
		svnCommitArg2.setAttribute("value", "./" + Globals.TESTING_PROJECT_NAME
				+ "/" + Globals.TESTING_TEST_FOLDER);
		Element svnCommitArg3 = new Element("arg");
		svnCommitArg3.setAttribute("value", "-m");
		Element svnCommitArg4 = new Element("arg");
		svnCommitArg4.setAttribute("value", "chariott test results");

		svnCommit.addContent(svnCommitArg1);
		svnCommit.addContent(svnCommitArg2);
		svnCommit.addContent(svnCommitArg3);
		svnCommit.addContent(svnCommitArg4);

		commitTarget.addContent(svnCommit);

		return commitTarget;
	}

	/**
	 * writes target Element for allTarget that includes the shut down
	 * instruction
	 * 
	 * @return Element
	 */
	private Element allTarget() {
		Element allTarget = new Element("target");
		allTarget.setAttribute("name", "all");
		allTarget.setAttribute("depends", "commit");

		// Element shutDown = new Element("exec");
		// shutDown.setAttribute("executable", "sudo");
		//
		// //
		// Element shutDownArg1 = new Element("arg");
		// shutDownArg1.setAttribute("value", "poweroff");
		// shutDown.addContent(shutDownArg1);
		//
		// Element shutDownArg2 = new Element("arg");
		// shutDownArg2.setAttribute("value", "-h");
		// shutDown.addContent(shutDownArg2);
		// //
		// Element shutDownArg3 = new Element("arg");
		// shutDownArg3.setAttribute("value", "now");
		// shutDown.addContent(shutDownArg3);
		// //
		// Element shutDownArg4 = new Element("arg");
		// shutDownArg4.setAttribute("value", "&");
		// shutDown.addContent(shutDownArg4);

		// allTarget.addContent(shutDown);

		return allTarget;
	}

	/**
	 * outputs the generated script files to the FileSystem and returns a File
	 * object with the script descriptions
	 * 
	 * @param root
	 * @param nodeNumber
	 * @return
	 */
	public File outputFile(Element root, int nodeNumber) {
		XMLOutputter xmlOutput = new XMLOutputter();
		xmlOutput.escapeElementEntities("/");
		xmlOutput.escapeAttributeEntities("/");
		xmlOutput.setFormat(Format.getPrettyFormat());

		try {
			// create the folder for generated scripts if it's not there
			File generatedScriptsFolder = new File("generatedScripts");
			if (!generatedScriptsFolder.isDirectory())
				generatedScriptsFolder.mkdir();

			// outputs the xml format into an xml file
			xmlOutput.output(root, new FileWriter(
					"generatedScripts/chariott-test" + nodeNumber + ".xml"));

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return (new File("generatedScripts/chariott-test" + nodeNumber + ".xml"));

	}

	public static void main(String[] args) {
	}
}
